﻿using System;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Media;

namespace FirstFloor.Documents.Pdf
{
    /// <summary>
    /// Provides various parser utility functions.
    /// </summary>
    internal static class ParserUtility
    {
        /// <summary>
        /// Parses an object identifier from given string starting at specified index.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="startIndex">The start index.</param>
        /// <returns></returns>
        public static ObjectIdentifier ParseIdentifier(string value, int startIndex)
        {
            int endIndex;
            int number = ParserHelper.ParseInt32(value, startIndex, out endIndex);
            int generation = ParserHelper.ParseInt32(value, endIndex + 1);

            return new ObjectIdentifier(number, generation);
        }

        /// <summary>
        /// Parses an integer value from given byte array where the value is stored in ASCII.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="length">The length.</param>
        /// <returns></returns>
        public static int ParseAsciiInt32(byte[] data, int offset, int length)
        {
            var result = 0;
            for (int i = offset; i < offset + length; i++) {
                result = result * 10 + (data[i] - '0');
            }
            return result;
        }

        /// <summary>
        /// Parses the integer value from given byte array stored with high-order byte first.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="length">The length.</param>
        /// <returns></returns>
        public static int ParseInt32(byte[] data, int offset, int length)
        {
            var result = 0;
            for (int i = offset; i < offset + length; i++) {
                result = (result << 8) + data[i];
            }
            return result;
        }

        /// <summary>
        /// Parses a hexacimal value.
        /// </summary>
        /// <param name="c">The c.</param>
        /// <returns></returns>
        public static int ParseHex(int c)
        {
            if (c >= '0' && c <= '9') {
                return c - '0';
            }
            if (c >= 'a' && c <= 'f') {
                return c - 'a' + 10;
            }
            if (c >= 'A' && c <= 'F') {
                return c - 'A' + 10;
            }
            throw new PdfParseException(Resources.InvalidHexChar, (char)c);
        }

        /// <summary>
        /// Creates a rectangle from specified ArrayObject.
        /// </summary>
        /// <param name="o">The o.</param>
        /// <returns></returns>
        public static Rect CreateRectangle(this ArrayObject o)
        {
            var x1 = o.Get<double>(0);
            var y1 = o.Get<double>(1);
            var x2 = o.Get<double>(2);
            var y2 = o.Get<double>(3);

            return new Rect(new Point(x1, y1), new Point(x2, y2));
        }

        /// <summary>
        /// Creates a matrix from specified ArrayObject.
        /// </summary>
        /// <param name="o">The o.</param>
        /// <returns></returns>
        public static Matrix CreateMatrix(this ArrayObject o)
        {
            return new Matrix(o.Get<double>(0), o.Get<double>(1), o.Get<double>(2), o.Get<double>(3), o.Get<double>(4), o.Get<double>(5));
        }

        /// <summary>
        /// Converts specified object array to a typed array.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <returns></returns>
        public static T[] ToArray<T>(this object[] args)
        {
            if (args != null) {
                var result = new T[args.Length];
                for (int i = 0; i < args.Length; i++) {
                    result[i] = Cast<T>(args[i]);
                }
                return result;
            }
            return null;
        }

        /// <summary>
        /// Casts the specified value to given type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static T Cast<T>(object value)
        {
            Type t = typeof(T);
            if (t == typeof(object)){
                return (T)value;
            }
            // unbox double values before casting to integers and bytes
            if (value.GetType() == typeof(double)) {
                if (t == typeof(int)) {
                    value = (int)(double)value;      // unbox before cast
                }
                else if (t == typeof(byte)) {
                    value = (byte)(double)value;
                }
            }

            if (t != value.GetType()) {
                throw new PdfParseException(Resources.CouldNotCastValue, value.GetType().FullName, t.FullName);
            }
            return (T)value;
        }

        /// <summary>
        /// Concatenates the specified matrices.
        /// </summary>
        /// <param name="m1">The m1.</param>
        /// <param name="m2">The m2.</param>
        /// <returns></returns>
        public static Matrix Concat(this Matrix m1, Matrix m2)
        {
            return m1.Concat(m2.M11, m2.M12, m2.M21, m2.M22, m2.OffsetX, m2.OffsetY);
        }

        /// <summary>
        /// Concatenates the matrix with specified values.
        /// </summary>
        /// <param name="m">The matrix.</param>
        /// <param name="m11">The M11.</param>
        /// <param name="m12">The M12.</param>
        /// <param name="m21">The M21.</param>
        /// <param name="m22">The M22.</param>
        /// <param name="offsetX">The offset X.</param>
        /// <param name="offsetY">The offset Y.</param>
        /// <returns></returns>
        public static Matrix Concat(this Matrix m, double m11, double m12, double m21, double m22, double offsetX, double offsetY)
        {
            return new Matrix(
                m.M11 * m11 + m.M12 * m21,
                m.M11 * m12 + m.M12 * m22,
                m.M21 * m11 + m.M22 * m21,
                m.M21 * m12 + m.M22 * m22,
                m.OffsetX * m11 + m.OffsetY * m21 + offsetX,
                m.OffsetX * m12 + m.OffsetY * m22 + offsetY);
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this Enum value)
        {
            return value.ToString();
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this Matrix value)
        {
            return value.ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this Color value)
        {
            return value.ToString();
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this Uri value)
        {
            return value.ToString();
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this int value)
        {
            return value.ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Formats the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static string Format(this double value)
        {
            return string.Format(CultureInfo.InvariantCulture, "{0:0.###}", value);
        }
    }
}
